﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Linq;
namespace DataModel
{
    public static class ChangeTracking
    {
        public static void SetRevertValue(DataModelBase ths, string PropertyName, object OldValue)
        {
            bool Trigger = !IsChanged;
            if (!BChanges.Any((CB) => CB.PropertyName == PropertyName))
            {
                BChanges.Add(new ChangeBackwards(ths, PropertyName, OldValue));
            }
            if(Trigger)
                OnPendingChangesChanged();
        }
        public static void SetComitNewAction(DataModelBase ths, string PropertyName, object NewValue, Action<DataModelBase, string, object> ComitPropertyValueAction)
        {
            bool Trigger = !IsChanged;

            foreach (var item in FChanges.Where((CF) => CF.PropertyName == PropertyName && CF.ths==ths).ToArray())
            {
                FChanges.Remove(item);
            }
            FChanges.Add(new ChangeForwards(ths, PropertyName, NewValue, ComitPropertyValueAction));

            if (Trigger)
                OnPendingChangesChanged();
        }
        public static void SetComitAction(DataModelBase ths, string PropertyName, object NewValue, Action<DataModelBase, string, object> ComitPropertyValueAction)
        {
            var CF = new ChangeForwards(ths, PropertyName, NewValue, ComitPropertyValueAction);
            CF.Commit();
        }

        static List<ChangeForwards> FChanges = new List<ChangeForwards>();
        static List<ChangeBackwards> BChanges = new List<ChangeBackwards>();
        public static  void RejectChanges()
        {
            foreach (var item in BChanges)
            {
                item.Revert();
            }
            BChanges.Clear();
            FChanges.Clear();
            Errors = 0;
            OnPendingChangesChanged();
        }
        //public static void Undo()
        //{
        //    var F = FChanges.Last();
        //    var B = BChanges.Last();
        //    if (F.ths == B.ths && F.PropertyName == B.PropertyName)
        //    {
        //Don't use RemoveAt here because it breaks as you modify the colleciton as you go. if you want to use remove at 
        //you must go through the collection in reverse order
        //        B.Revert();
        //        foreach (var item in BChanges.Where((C)=>C.ths == B.ths && C.PropertyName == B.PropertyName).Select((C,i)=>i))
        //        {
        //            BChanges.RemoveAt(item);
        //        }
        //        foreach (var item in FChanges.Where((C) => C.ths == F.ths && C.PropertyName == F.PropertyName).Select((C, i) => i))
        //        {
        //            FChanges.RemoveAt(item);
        //        }
        //    }
        //}
        //public static bool CanUndo {
        //    get {
        //        var F = FChanges.Last();
        //        var B = BChanges.Last();
        //        return (F.ths == B.ths && F.PropertyName == B.PropertyName);
        //    }}

        public static void AcceptChanges()
        {
            if (IsErrorFree)
            {
                foreach (var item in FChanges.ToArray())
                {
                    item.Commit();
                }
                FChanges.Clear();
                BChanges.Clear();
                OnPendingChangesChanged();                
            }

        }

        public static bool IsChanged
        {
            get { return FChanges.Any() | BChanges.Any(); }
        }
        public static int PendingChanges
        {
            get { return Math.Max(FChanges.Count,BChanges.Count); }
        }

        static int Errors;
        public static void AddError()
        {
            System.Threading.Interlocked.Increment(ref Errors);
            OnErrorsChanged();
        }
        public static void RemoveError()
        {
            System.Threading.Interlocked.Decrement(ref Errors);
            OnErrorsChanged();
        }
        public static bool IsErrorFree { get { return Errors == 0; } }

        static void OnErrorsChanged()
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(null, EventArgs.Empty);
            }
        }
        public static event EventHandler ErrorsChanged;

        static void OnPendingChangesChanged()
        {
            if (PendingChangesChanged != null)
            {
                PendingChangesChanged(null, EventArgs.Empty);
            }
        }
        public static event EventHandler PendingChangesChanged;
    }

    public class ChangeForwards
    {
        public ChangeForwards(DataModelBase ths, string PropertyName, object NewValue, Action<DataModelBase, string, object> ComitPropertyValueAction)
        {
            this.ths = ths;
            this.PropertyName = PropertyName;
            this.NewValue = NewValue;
            this.ComitPropertyValueAction = ComitPropertyValueAction;
        }

        public DataModelBase ths { get; set; }
        public string PropertyName { get; set; }
        public object NewValue { get; set; }
        public Action<DataModelBase, string, object> ComitPropertyValueAction { get; set; }

        public void Commit()
        {
            ComitPropertyValueAction(ths, PropertyName, NewValue);
        }
    }

    public class ChangeBackwards
    {
        public ChangeBackwards(DataModelBase ths, string PropertyName, object OldValue)
        {
            this.ths = ths;
            this.PropertyName = PropertyName;
            this.OldValue = OldValue;
        }
        public DataModelBase ths { get; set; }
        public string PropertyName { get; set; }
        public object OldValue { get; set; }

        public void Revert()
        {
            ths.GetType().GetProperty(PropertyName).GetSetMethod().Invoke(ths, new object[] { OldValue });
        }

    }


    public class SaveChangesCommand:ICommand
    {
        public SaveChangesCommand()
        {
            ChangeTracking.ErrorsChanged += new EventHandler(PossibleChange);
            ChangeTracking.PendingChangesChanged += new EventHandler(PossibleChange);
        }

        void PossibleChange(object sender, EventArgs e)
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return ChangeTracking.IsErrorFree && ChangeTracking.IsChanged;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            ChangeTracking.AcceptChanges();
        }

        #endregion
    }
    public class CancelChangesCommand:ICommand
    {

        public CancelChangesCommand()
        {
            ChangeTracking.PendingChangesChanged += new EventHandler(PossibleChange);
        }

        void PossibleChange(object sender, EventArgs e)
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return ChangeTracking.IsChanged;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            ChangeTracking.RejectChanges();
        }

        #endregion
    }
    //public class UndoCommand:ICommand
    //{
    //    public UndoCommand()
    //    {
    //        ChangeTracking.PendingChangesChanged += new EventHandler(ChangeTracking_PendingChangesChanged);
    //    }

    //    void ChangeTracking_PendingChangesChanged(object sender, EventArgs e)
    //    {
    //        if (CanExecuteChanged != null)
    //        {
    //            CanExecuteChanged(this, e);
    //        }
    //    }   

    //    #region ICommand Members

    //    public bool CanExecute(object parameter)
    //    {
    //        return ChangeTracking.CanUndo;
    //    }

    //    public event EventHandler CanExecuteChanged;

    //    public void Execute(object parameter)
    //    {
    //        ChangeTracking.Undo();
    //    }

    //    #endregion
    //}
}
